
package models.spider.request;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import javax.xml.parsers.DocumentBuilderFactory;

import org.apache.commons.lang3.StringUtils;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.conn.ssl.X509HostnameVerifier;
import org.apache.http.entity.ContentType;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.client.LaxRedirectStrategy;
import org.apache.http.impl.conn.PoolingClientConnectionManager;
import org.apache.http.params.CoreProtocolPNames;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;
import org.w3c.dom.Document;

import play.Logger;

/**
 * NOTE :::
 * http://www.playframework.org/documentation/1.2.5/libs#ParsingXMLusingXPath
 * 
 * @author laurent@opprecht.info
 */
public class DeprecatedRequest {

    public static class TrustAllManager implements X509TrustManager {

        private static boolean isInstalled = false;

        static void install() {
            if (isInstalled) {
                return;
            }
            TrustManager[] trustAllCerts = new TrustManager[] { new TrustAllManager() };

            SSLContext sc;
            try {
                sc = SSLContext.getInstance("SSL");
            } catch (NoSuchAlgorithmException e1) {
                Logger.error("NoSuchAlgorithmException", e1);
                return;
            }
            try {
                sc.init(null, trustAllCerts, new java.security.SecureRandom());
            } catch (KeyManagementException e2) {
                Logger.error("KeyManagementException", e2);
                return;
            }
            HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());

            isInstalled = true;
        }

        @SuppressWarnings("deprecation")
		public static HttpClient wrap(final HttpClient base) {
            try {
                SSLContext ctx = SSLContext.getInstance("TLS");
                X509TrustManager tm = new TrustAllManager();
                X509HostnameVerifier verifier = new TrustHostNameVerifier();
                ctx.init(null, new TrustManager[] { tm }, null);
                SSLSocketFactory ssf = new SSLSocketFactory(ctx);
                ssf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
                ssf.setHostnameVerifier(verifier);
                ClientConnectionManager ccm = base.getConnectionManager();
                SchemeRegistry sr = ccm.getSchemeRegistry();
                sr.register(new Scheme("https", ssf, 443));
                return new DefaultHttpClient(ccm, base.getParams());
            } catch (Exception ex) {
                ex.printStackTrace();
                return null;
            }
        }

        @Override
        public java.security.cert.X509Certificate[] getAcceptedIssuers() {
            return null;
        }

        @Override
        public void checkClientTrusted(final java.security.cert.X509Certificate[] certs, final String authType) {
        }

        @Override
        public void checkServerTrusted(final java.security.cert.X509Certificate[] certs, final String authType) {
        }

    }

    public static class TrustHostNameVerifier implements X509HostnameVerifier {

        @Override
        public boolean verify(final String arg0, final SSLSession arg1) {
            return true;
        }

        @Override
        public void verify(final String host, final SSLSocket ssl) throws IOException {
            ;
        }

        @Override
        public void verify(final String host, final X509Certificate cert) throws SSLException {
            ;
        }

        @Override
        public void verify(final String host, final String[] cns, final String[] subjectAlts) throws SSLException {
            ;
        }

    }

    public class Body {

        protected byte[]   raw  = null;
        protected String   text = null;
        protected Document xml  = null;
        protected Document html = null;

        protected Body(final byte[] raw) {
            this.raw = raw;
        }

        protected Body() {
            this.raw = null;
        }

        public String asText() {
            if (raw == null) {
                return null;
            }
            if (text != null) {
                return text;
            }

            String charset = getCharset();
            try {
                if (charset != null) {
                    text = new String(raw, charset);
                } else {
                    text = new String(raw);
                }
            } catch (java.io.UnsupportedEncodingException e) {
            }
            return text;
        }

        public Document asXml() {
            if (raw == null) {
                return null;
            }
            if (xml != null) {
                return xml;
            }
            try {
                DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
                javax.xml.parsers.DocumentBuilder builder = factory.newDocumentBuilder();
                Document result = builder.parse(url.toString());
                xml = result;
                return result;
            } catch (Exception ex) {
                Logger.error("asXml", ex);
            }
            return null;
        }

        public Document asHtml() {
            if (raw == null) {
                return null;
            }
            if (html != null) {
                return html;
            }
            try {
                DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
                factory.setValidating(false);
                factory.setNamespaceAware(true);
                factory.setIgnoringComments(false);
                factory.setIgnoringElementContentWhitespace(false);
                factory.setExpandEntityReferences(false);
                javax.xml.parsers.DocumentBuilder builder = factory.newDocumentBuilder();
                Document result = builder.parse(url.toString());
                html = result;
                return result;
            } catch (Exception ex) {
                Logger.error("asHtml", ex);
            }
            return null;

        }

    }

    public static DeprecatedRequest get(final String url) {
        if (StringUtils.isEmpty(url)) {
            return DeprecatedRequest.EMPTY;
        } else {

            try {
                return new DeprecatedRequest(new URL(url));
            } catch (MalformedURLException e) {
                Logger.error("invalid url " + url, e);
                return DeprecatedRequest.EMPTY;
            }
        }
    }

    static {
        TrustAllManager.install();
    }

    public static final DeprecatedRequest EMPTY           = new DeprecatedRequest(null);
    protected URL               url             = null;
    protected Header[]          headers         = new Header[] {};
    protected int               responseCode    = 0;
    protected String            contentEncoding = "";
    protected ContentType       contentType     = null;
    protected int               contentLength   = 0;
    protected DeprecatedRequest.Body      body            = null;

    public DeprecatedRequest(final URL url) {
        this.url = url;
    }

    public String getUrl() {
        return url.toString();
    }

    public boolean isLoaded() {
        return body != null;
    }

    public boolean isValid() {
        load();
        return 200 <= responseCode && responseCode <= 299;
    }

    public int getResponseCode() {
        load();
        return responseCode;
    }

    public boolean isHtml() {
        load();
        String mime = getMimeType();
        if (mime == null) {
            return false;
        }
        return mime.toLowerCase().indexOf("html") >= 0;
    }

    public boolean isXml() {
        load();
        String mime = getMimeType();
        if (mime == null) {
            return false;
        }
        return mime.toLowerCase().indexOf("xml") >= 0;
    }

    public boolean isJson() {
        load();
        String mime = getMimeType();
        if (mime == null) {
            return false;
        }
        return mime.toLowerCase().indexOf("json") >= 0;
    }

    public String getContentType() {
        load();
        return (contentType == null) ? "" : contentType.toString();
    }

    public DeprecatedRequest.Body body() {
        load();
        return body;
    }

    public String getCharset() {
        load();
        return (contentType == null) ? "" : contentType.getCharset().toString();
    }

    public String getMimeType() {
        load();
        String contentType = getContentType();

        if (StringUtils.isEmpty(contentType)) {
            return null;
        }

        String[] parts = contentType.split(";");
        if (parts.length == 0) {
            return null;
        }
        return parts[0].trim();

    }

    public boolean connect() {
        body = new DeprecatedRequest.Body();
        if (url == null) {
            return false;
        }
        
        PoolingClientConnectionManager cm = new PoolingClientConnectionManager();
        cm.setMaxTotal(100);
        DefaultHttpClient httpClient = new DefaultHttpClient(cm);
        httpClient.setRedirectStrategy(new LaxRedirectStrategy());
        httpClient.getParams().setParameter("http.tcp.nodelay", true);
        HttpParams params = httpClient.getParams();
        HttpConnectionParams.setConnectionTimeout(params, 120 * 1000);
        HttpContext context = new BasicHttpContext();
        context.setAttribute(CoreProtocolPNames.USER_AGENT, "Spider RE");
        HttpClient client = TrustAllManager.wrap(httpClient);
	    
        HttpGet httpGet = null;
        try {
            httpGet = new HttpGet(url.toURI());
        } catch (Exception ex) {
            return false;
        }
        try {
            HttpResponse response = client.execute(httpGet, context);
            headers = response.getAllHeaders();
            responseCode = response.getStatusLine().getStatusCode();
            HttpEntity entity = response.getEntity();
            if (entity != null) {
                byte[] raw = EntityUtils.toByteArray(entity);
                body = new DeprecatedRequest.Body(raw);
                contentEncoding = (entity.getContentEncoding() != null) ? entity.getContentEncoding().getValue() : null;
                contentType = ContentType.getOrDefault(entity);
            } else {
                body = new DeprecatedRequest.Body();
                contentEncoding = "";
            }
            httpGet.releaseConnection();

        } catch (Exception ex) {
            if (httpGet != null) {
                httpGet.abort();
            }
            Logger.error("Connection faileds", ex);
            httpGet.releaseConnection();
            return false;
        }

        return true;

    }

    /**
     * If resource is not yet loaded call connect to fetch it. Otherwise exit.
     * Used to lazy load the resource if required.
     */
    protected void load() {
        if (isLoaded()) {
            return;
        }
        connect();
    }
}
